package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/


import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * "Thus saith the Lord GOD; When I shall have gathered the house of Israel 
 *  from the people among whom they are scattered, and shall be sanctified 
 *  in them in the sight of the heathen, then shall they dwell in their land 
 *  that I have given to my servant Jacob." (Ezekiel 28:25)
 * 
 * ==========
 * 
 * Simple utility for using csv data files. For some purposes simple and flat 
 * file with comma separated value rows is more convinient way to write
 * and read the data than corresponding xml or json structure.
 * 
 * @author jukka.rahkonen@iki.fi
 */
public class CsvTokenizer extends Jugile {
	
	enum State {
		FLD,
		STR,
	}
	
	private char delimiter = ',';
	private PullReader pr = null;
	
	private enum TokenType { NL, DELIM, EOF }
	private class Token {
		public TokenType type;
		public String value; // preceding buffer value
		public Token(TokenType t, String v) { type = t; value = v; }
		public String toString() { return "FLD:" + type.toString() +":"+value; }
	}
	
	private Token tNL(String v) { return new Token(TokenType.NL,v); }
	private Token tEOF(String v) { return new Token(TokenType.EOF,v); }
	private Token tDELIM(String v) { return new Token(TokenType.DELIM,v); }
	
	public CsvTokenizer(String fname) {
		pr = new PullReader(getResource(fname));
	}
	public CsvTokenizer(PullReader pr) {
		this.pr = pr;
	}
	public CsvTokenizer(InputStream is) {
		pr = new PullReader(is);
	}
	
	public void close() {
		pr.close();
	}
	
	public void setDelimiter(char ch) { delimiter = ch; }
	
	public static CsvTokenizer parseString(String data) { return parseString(data,',');	}
	public static CsvTokenizer parseString(String data, char delimiter) {
		CsvTokenizer t = new CsvTokenizer(new PullReader(data));
		t.setDelimiter(delimiter);
		return t;
	}
	
	public boolean hasNext() {
		return pr.hasNext();
	}
	
	public Token next() {
		Buffer buf = new Buffer();
		State st = State.FLD;
		while (pr.hasNext()) {
			char ch = pr.next();
			//print("ch: " + ch);
			if (ch == '\\') {
				char p1 = pr.next();
				if (p1 == 'n') buf.add("\n");
				else if (p1 == 't') buf.add("\t");
				else if (p1 == 'r') buf.add("\r");
				else buf.add(p1);
				continue;
			}

			if (ch == '\n') return tNL(buf.flush());

			if (st == State.STR) {
				if (ch == '"') {
					char p1 = pr.peek();
					if (p1 == '"') { 
						buf.add('"'); 
						pr.skip(1);
						continue;
					} else {
						st = State.FLD;
						continue;
					}
				}
				buf.add(ch);
				continue;
			}
			if (ch == '"') {
				st = State.STR;
				continue;
			}
			
			if (ch == delimiter) return tDELIM(buf.flush());

			// eat controls
			if (ch == '\r') continue;
			if (ch == '\f') continue;
			if (ch == '\b') continue;

			buf.add(ch);
		}
		return tEOF(buf.flush());
	}
	
	private List<String> cols;
	public boolean readLine() {
		cols = new ArrayList<String>();
		while (hasNext()) {
			Token t = next();
			//print("token: " + t);
			cols.add(t.value);
			if (t.type == TokenType.NL || t.type == TokenType.EOF) return true;
		}
		if (cols.size() > 0) { // TODO: clean this up
			cols.add("");
			return true; // if last line ends with ","
		}
		return false;
	}
	
	public String col(int col) { return cols.get(col); }
	public int cols() { return cols.size(); }
	public List<String> getCols() { return cols; }
	public List<String> getCols(int startidx) { return getCols(startidx, cols.size()); }
	public List<String> getCols(int startidx, int endidx) { return cols.subList(startidx, endidx); }

	public String dumpCols() {
		return joinQuoted(cols);
	}
	public String dumpCols(int startidx) {
		return joinQuoted(getCols(startidx));
	}
	
	// ---------- scrap book ------------
	
	public static void main(String args[]) {
		CsvTokenizer c;
//		c = new CsvTokenizer("data/classification.csv");
//		while (c.hasNext()) {
//			print("token: " + c.next());
//		}
		c = new CsvTokenizer("data/classification.csv");
		while (c.readLine()) {
			print("num: " + c.getCols().size() + ": " + c.dumpCols());
		}
		c = CsvTokenizer.parseString("10,,10,\"foo,\"\"bar\"");
		while (c.readLine()) {
			print("num: " + c.getCols().size() + ": " + c.dumpCols());
			print("num: " + c.getCols().size() + ": " + c.dumpCols(1));			
		}
		
		c = CsvTokenizer.parseString("100000,\"Areena\",1");
		while (c.readLine()) {
			print("num: " + c.getCols().size() + ": " + c.dumpCols());
		}
		c = CsvTokenizer.parseString("100000,\"Areena\",");
		while (c.readLine()) {
			print("num: " + c.getCols().size() + ": " + c.dumpCols());
		}
		print("done");
		
	}

}
